from django.conf import settings
import logging
from typing import Dict, Optional, Union, List
import os
import io
from io import BytesIO
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, ListFlowable, Image, ListItem, PageBreak
from reportlab.lib.units import cm, mm, inch
from reportlab.lib.enums import TA_JUSTIFY, TA_LEFT, TA_CENTER, TA_RIGHT
import re
from datetime import datetime
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.utils import simpleSplit

logger = logging.getLogger(__name__)

class PDFExportError(Exception):
    """PDF导出服务异常类"""
    def __init__(self, message, code=None, http_status=500):
        self.message = message
        self.code = code
        self.http_status = http_status
        super().__init__(message)

class PDFExportService:
    """PDF导出服务类，提供将笔记导出为PDF的功能"""
    
    def __init__(self):
        """初始化PDF导出服务"""
        # 注册中文字体
        self.fonts_registered = False
        self._register_fonts()
        
        # 页面配置
        self.page_width, self.page_height = A4
        self.margin = 2 * cm
        
        # 样式配置
        self.styles = None
        self._init_styles()
    
    def _register_fonts(self):
        """注册字体，尝试多种中文字体"""
        if self.fonts_registered:
            return
            
        try:
            # 尝试注册微软雅黑字体
            fonts_to_try = [
                ('simhei', os.path.join(settings.BASE_DIR, 'static/fonts/simhei.ttf')),
                ('simsun', os.path.join(settings.BASE_DIR, 'static/fonts/simsun.ttc')),
                ('msyh', os.path.join(settings.BASE_DIR, 'static/fonts/msyh.ttf')),
                ('simhei', 'C:/Windows/Fonts/simhei.ttf'),
                ('simsun', 'C:/Windows/Fonts/simsun.ttc'),
                ('msyh', 'C:/Windows/Fonts/msyh.ttf'),
                ('simkai', 'C:/Windows/Fonts/simkai.ttf'),
            ]
            
            for font_name, font_path in fonts_to_try:
                if font_name not in pdfmetrics.getRegisteredFontNames() and os.path.exists(font_path):
                    pdfmetrics.registerFont(TTFont(font_name, font_path))
                    logger.info(f"成功注册字体: {font_name} 从 {font_path}")
                    self.fonts_registered = True
                    
            if not self.fonts_registered:
                logger.warning("未找到任何中文字体，PDF将使用默认字体，可能无法正确显示中文")
        except Exception as e:
            logger.warning(f"注册中文字体失败: {str(e)}")
    
    def _init_styles(self):
        """初始化文档样式"""
        self.styles = getSampleStyleSheet()
        
        # 添加标题样式
        self.title_style = ParagraphStyle(
            'CustomTitle',
            parent=self.styles['Title'],
            fontName=self._get_available_chinese_font(),
            fontSize=24,
            spaceAfter=16,
            alignment=TA_CENTER,
            leading=28
        )
        
        # 创建中文兼容样式
        self.normal_style = ParagraphStyle(
            'CustomNormal',
            parent=self.styles['Normal'],
            fontName=self._get_available_chinese_font(),
            fontSize=11,
            leading=16,
            spaceAfter=8
        )
        
        self.heading1_style = ParagraphStyle(
            'CustomHeading1',
            parent=self.styles['Heading1'],
            fontName=self._get_available_chinese_font(),
            fontSize=18,
            leading=22,
            spaceAfter=12,
            spaceBefore=12,
            textColor=colors.darkblue
        )
        
        self.heading2_style = ParagraphStyle(
            'CustomHeading2',
            parent=self.styles['Heading2'],
            fontName=self._get_available_chinese_font(),
            fontSize=16,
            leading=20,
            spaceAfter=10,
            spaceBefore=10,
            textColor=colors.darkblue
        )
        
        self.heading3_style = ParagraphStyle(
            'CustomHeading3',
            parent=self.styles['Heading3'],
            fontName=self._get_available_chinese_font(),
            fontSize=14,
            leading=18,
            spaceAfter=8,
            spaceBefore=8,
            textColor=colors.darkblue
        )
        
        # 定义列表样式
        self.bullet_style = ParagraphStyle(
            'CustomBullet',
            parent=self.normal_style,
            leftIndent=20,
            firstLineIndent=0,
            spaceBefore=2,
            spaceAfter=5
        )
        
        # 代码块样式
        self.code_style = ParagraphStyle(
            'CustomCode',
            parent=self.styles['Code'],
            fontName='Courier',
            fontSize=9,
            leading=12,
            leftIndent=36,
            rightIndent=36,
            spaceBefore=8,
            spaceAfter=8,
            backColor=colors.lightgrey
        )
    
    def export_note_to_pdf(self, note_content: str, title: str = "") -> BytesIO:
        """
        将笔记内容导出为PDF
        
        Args:
            note_content: Markdown格式的笔记内容
            title: 笔记标题
            
        Returns:
            BytesIO: 包含PDF内容的BytesIO对象
        """
        try:
            # 创建一个BytesIO对象来存储PDF
            buffer = BytesIO()
            
            # 设置页面尺寸和边距
            doc = SimpleDocTemplate(
                buffer,
                pagesize=A4,
                rightMargin=self.margin,
                leftMargin=self.margin,
                topMargin=self.margin,
                bottomMargin=self.margin,
                title=title or "导出笔记"
            )
            
            # 解析Markdown内容为PDF元素
            elements = self._parse_markdown(note_content, title)
            
            # 构建PDF文档
            doc.build(elements, onFirstPage=self._add_header_footer, onLaterPages=self._add_header_footer)
            
            # 重置文件指针到开头
            buffer.seek(0)
            return buffer
            
        except Exception as e:
            logger.error(f"导出PDF时出错: {str(e)}", exc_info=True)
            raise PDFExportError(f"导出PDF失败: {str(e)}", code="EXPORT_ERROR")
    
    def _add_header_footer(self, canvas, doc):
        """为每页添加页眉页脚"""
        width, height = A4
        
        # 页脚 - 简单页码
        canvas.saveState()
        canvas.setFont(self._get_available_chinese_font(), 9)
        canvas.setFillColor(colors.grey)
        
        # 添加页码
        page_number = f"{doc.page}"
        canvas.drawCentredString(width / 2, 1 * cm, page_number)
        
        canvas.restoreState()
    
    def _get_available_chinese_font(self):
        """获取可用的中文字体"""
        # 按优先级尝试不同字体
        for font in ['simhei', 'msyh', 'simsun', 'simkai', 'NotoSansSC', 'NotoSerifSC']:
            if font in pdfmetrics.getRegisteredFontNames():
                return font
        
        # 返回默认字体
        return 'Helvetica'
    
    def _sanitize_html(self, text):
        """
        清理和修复HTML标签，确保标签正确嵌套
        """
        # 已知问题模式的修复
        # 1. 修复类似 <font><i>text</font></i> 的错误嵌套
        text = re.sub(r'<font([^>]*)><i>([^<]*)</font></i>', r'<font\1><i>\2</i></font>', text)
        text = re.sub(r'<i><font([^>]*)>([^<]*)</i></font>', r'<font\1><i>\2</i></font>', text)
        
        # 2. 修复未闭合标签
        open_tags = {
            '<b>': '</b>',
            '<i>': '</i>',
            '<u>': '</u>',
            '<strike>': '</strike>',
            '<super>': '</super>',
            '<sub>': '</sub>',
        }
        
        for open_tag, close_tag in open_tags.items():
            # 计算开标签和闭标签的数量
            open_count = text.count(open_tag)
            close_count = text.count(close_tag)
            
            # 如果开标签比闭标签多，在末尾添加缺少的闭标签
            if open_count > close_count:
                text += close_tag * (open_count - close_count)
            
            # 如果闭标签比开标签多，在开头添加缺少的开标签
            # 这种情况不太可能，但为了完整性添加此处理
            if close_count > open_count:
                text = open_tag * (close_count - open_count) + text
        
        # 3. font标签的特殊处理
        # 查找所有font标签的开始和结束
        font_open_matches = list(re.finditer(r'<font[^>]*>', text))
        font_close_matches = list(re.finditer(r'</font>', text))
        
        # 如果有不平衡的标签
        if len(font_open_matches) != len(font_close_matches):
            # 创建一个新的字符串，逐步构建正确的标签结构
            result = []
            last_end = 0
            open_font_tags = []
            
            # 按顺序遍历文本
            i = 0
            j = 0
            while i < len(font_open_matches) and j < len(font_close_matches):
                open_tag = font_open_matches[i]
                close_tag = font_close_matches[j]
                
                # 如果开标签在闭标签之前
                if open_tag.start() < close_tag.start():
                    # 添加从上次结束到当前开标签的文本
                    result.append(text[last_end:open_tag.start()])
                    # 添加开标签
                    result.append(text[open_tag.start():open_tag.end()])
                    last_end = open_tag.end()
                    open_font_tags.append(open_tag.group())
                    i += 1
                else:
                    # 添加从上次结束到当前闭标签的文本
                    result.append(text[last_end:close_tag.start()])
                    # 如果有匹配的开标签，添加闭标签
                    if open_font_tags:
                        result.append('</font>')
                        open_font_tags.pop()
                    last_end = close_tag.end()
                    j += 1
            
            # 处理剩余的开标签
            while i < len(font_open_matches):
                open_tag = font_open_matches[i]
                result.append(text[last_end:open_tag.start()])
                result.append(text[open_tag.start():open_tag.end()])
                last_end = open_tag.end()
                open_font_tags.append(open_tag.group())
                i += 1
            
            # 处理剩余的闭标签
            while j < len(font_close_matches):
                close_tag = font_close_matches[j]
                result.append(text[last_end:close_tag.start()])
                if open_font_tags:
                    result.append('</font>')
                    open_font_tags.pop()
                last_end = close_tag.end()
                j += 1
            
            # 添加剩余文本
            result.append(text[last_end:])
            
            # 为任何未闭合的字体标签添加闭标签
            for _ in open_font_tags:
                result.append('</font>')
            
            text = ''.join(result)
        
        return text
    
    def _parse_markdown(self, markdown_text: str, title: str = "") -> List:
        """
        解析Markdown内容为ReportLab元素
        
        Args:
            markdown_text: Markdown文本
            title: 文档标题
            
        Returns:
            List: ReportLab元素列表
        """
        elements = []
        
        # 添加标题
        if title:
            elements.append(Paragraph(title, self.title_style))
            elements.append(Spacer(1, 0.5*cm))
        
        # 解析Markdown内容
        lines = markdown_text.split('\n')
        i = 0
        in_code_block = False
        code_buffer = []
        in_list = False
        list_items = []
        
        while i < len(lines):
            line = lines[i].strip()
            
            # 处理代码块
            if line.startswith('```') or line.startswith('~~~'):
                if in_code_block:
                    # 结束代码块
                    if code_buffer:
                        code_text = '\n'.join(code_buffer)
                        # 处理过长的代码行，自动添加换行
                        processed_lines = []
                        for code_line in code_buffer:
                            # 检查行长度
                            if len(code_line) > 80:  # 80字符作为宽度限制
                                # 处理长行
                                chunks = simpleSplit(code_line, self.code_style.fontName, 
                                                   self.code_style.fontSize, 
                                                   self.page_width - 2*self.margin - self.code_style.leftIndent - self.code_style.rightIndent)
                                processed_lines.extend(chunks)
                            else:
                                processed_lines.append(code_line)
                        
                        code_text = '\n'.join(processed_lines)
                        elements.append(Paragraph(code_text, self.code_style))
                    code_buffer = []
                    in_code_block = False
                else:
                    # 开始代码块
                    in_code_block = True
                    code_buffer = []
                i += 1
                continue
            
            if in_code_block:
                code_buffer.append(line)
                i += 1
                continue
            
            # 处理列表
            if line.startswith('- ') or line.startswith('* ') or re.match(r'^\d+\.\s', line):
                if not in_list:
                    in_list = True
                    list_items = []
                
                # 提取列表项的文本内容
                if line.startswith('- ') or line.startswith('* '):
                    list_text = line[2:].strip()
                    bullet_type = 'bullet'
                else:
                    list_text = re.sub(r'^\d+\.\s', '', line).strip()
                    bullet_type = 'numbered'
                
                # 处理内联标记
                list_text = self._process_inline_markup(list_text)
                # 清理HTML标签
                list_text = self._sanitize_html(list_text)
                
                try:
                    list_items.append(Paragraph(list_text, self.bullet_style))
                except Exception as e:
                    logger.warning(f"处理列表项时出错: {str(e)}，内容: {list_text}")
                    # 尝试使用纯文本格式添加
                    plain_text = re.sub(r'<[^>]+>', '', list_text)
                    list_items.append(Paragraph(plain_text, self.bullet_style))
                
                i += 1
                continue
            elif in_list:
                # 列表结束，添加列表元素
                list_flowable = ListFlowable(
                    list_items,
                    bulletType=bullet_type,
                    leftIndent=20,
                    spaceBefore=6,
                    spaceAfter=6
                )
                elements.append(list_flowable)
                in_list = False
                list_items = []
            
            # 处理分隔线
            if line == '---' or line == '***' or line == '___':
                elements.append(Spacer(1, 0.1*inch))
                elements.append(Paragraph('<hr/>', self.normal_style))
                elements.append(Spacer(1, 0.1*inch))
                i += 1
                continue
                
            # 处理标题
            if line.startswith('#'):
                heading_level = len(re.match(r'^#+', line).group())
                heading_text = line[heading_level:].strip()
                heading_text = self._process_inline_markup(heading_text)
                # 清理HTML标签
                heading_text = self._sanitize_html(heading_text)
                
                # 为一级标题前添加额外空间
                if heading_level == 1 and elements:
                    elements.append(Spacer(1, 0.2*inch))
                
                try:
                    if heading_level == 1:
                        elements.append(Paragraph(heading_text, self.heading1_style))
                    elif heading_level == 2:
                        elements.append(Paragraph(heading_text, self.heading2_style))
                    else:
                        elements.append(Paragraph(heading_text, self.heading3_style))
                except Exception as e:
                    logger.warning(f"处理标题时出错: {str(e)}，内容: {heading_text}")
                    # 尝试使用纯文本格式添加
                    plain_text = re.sub(r'<[^>]+>', '', heading_text)
                    if heading_level == 1:
                        elements.append(Paragraph(plain_text, self.heading1_style))
                    elif heading_level == 2:
                        elements.append(Paragraph(plain_text, self.heading2_style))
                    else:
                        elements.append(Paragraph(plain_text, self.heading3_style))
                
                i += 1
                continue
            
            # 处理普通段落
            elif line:
                # 处理粗体、斜体和链接
                processed_line = self._process_inline_markup(line)
                # 清理HTML标签
                processed_line = self._sanitize_html(processed_line)
                
                try:
                    elements.append(Paragraph(processed_line, self.normal_style))
                except Exception as e:
                    logger.warning(f"处理段落时出错: {str(e)}，内容: {processed_line}")
                    # 尝试使用纯文本格式添加
                    plain_text = re.sub(r'<[^>]+>', '', processed_line)
                    elements.append(Paragraph(plain_text, self.normal_style))
                
                i += 1
                continue
            
            # 处理空行
            else:
                elements.append(Spacer(1, 5))
                i += 1
        
        # 处理可能的未结束列表
        if in_list and list_items:
            list_flowable = ListFlowable(
                list_items,
                bulletType=bullet_type,
                leftIndent=20
            )
            elements.append(list_flowable)
        
        # 处理可能的未结束代码块
        if in_code_block and code_buffer:
            code_text = '\n'.join(code_buffer)
            elements.append(Paragraph(code_text, self.code_style))
        
        return elements
    
    def _process_inline_markup(self, text: str) -> str:
        """
        处理Markdown内联标记
        
        Args:
            text: 原始文本
            
        Returns:
            str: 处理后的文本
        """
        # 预处理：转义HTML特殊字符
        text = text.replace('&', '&amp;')
        text = text.replace('<', '&lt;')
        text = text.replace('>', '&gt;')
        
        # 使用占位符避免嵌套标记问题
        placeholders = {}
        placeholder_counter = 0
        
        def create_placeholder():
            nonlocal placeholder_counter
            placeholder = f"__PLACEHOLDER_{placeholder_counter}__"
            placeholder_counter += 1
            return placeholder
        
        # 1. 首先处理行内代码 (优先级最高，避免代码中的其他标记被解析)
        code_pattern = r'`([^`]+)`'
        def code_replacer(match):
            code_text = match.group(1)
            placeholder = create_placeholder()
            # 使用简单的字体变化而不是复杂标签
            placeholders[placeholder] = f'<code>{code_text}</code>'
            return placeholder
            
        text = re.sub(code_pattern, code_replacer, text)
        
        # 2. 处理粗体
        bold_pattern = r'\*\*([^\*]+)\*\*'
        def bold_replacer(match):
            bold_text = match.group(1)
            placeholder = create_placeholder()
            placeholders[placeholder] = f'<b>{bold_text}</b>'
            return placeholder
            
        text = re.sub(bold_pattern, bold_replacer, text)
        
        # 3. 处理斜体
        italic_pattern = r'\*([^\*]+)\*'
        def italic_replacer(match):
            italic_text = match.group(1)
            placeholder = create_placeholder()
            placeholders[placeholder] = f'<i>{italic_text}</i>'
            return placeholder
            
        text = re.sub(italic_pattern, italic_replacer, text)
        
        # 4. 处理链接
        link_pattern = r'\[(.*?)\]\((.*?)\)'
        def link_replacer(match):
            link_text = match.group(1)
            link_url = match.group(2)
            placeholder = create_placeholder()
            placeholders[placeholder] = f'<a href="{link_url}" color="blue">{link_text}</a>'
            return placeholder
            
        text = re.sub(link_pattern, link_replacer, text)
        
        # 5. 处理删除线
        strike_pattern = r'~~(.*?)~~'
        def strike_replacer(match):
            strike_text = match.group(1)
            placeholder = create_placeholder()
            placeholders[placeholder] = f'<strike>{strike_text}</strike>'
            return placeholder
            
        text = re.sub(strike_pattern, strike_replacer, text)
        
        # 6. 处理下划线
        underline_pattern = r'__([^_]+)__'
        def underline_replacer(match):
            underline_text = match.group(1)
            placeholder = create_placeholder()
            placeholders[placeholder] = f'<u>{underline_text}</u>'
            return placeholder
            
        text = re.sub(underline_pattern, underline_replacer, text)
        
        # 恢复所有占位符
        for placeholder, value in placeholders.items():
            text = text.replace(placeholder, value)
        
        # 处理<code>标签，转换为reportlab支持的格式
        text = text.replace('<code>', '<font face="Courier" size="9">')
        text = text.replace('</code>', '</font>')
        
        return text 